﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Surface;
using Microsoft.Surface.Presentation;
using Microsoft.Surface.Presentation.Controls;
using WPFMitsuControlsv2;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;

namespace SurfaceCardsGameKit
{
    /// <summary>
    /// Interaction logic for CardUC.xaml
    /// </summary>
    [TemplatePart(Name = CardUC.ElementGrid, Type = typeof(Grid))]
    [TemplatePart(Name = CardUC.ElementBookPage, Type = typeof(BookPage))]
    public partial class CardUC : SurfaceUserControl
    {

        #region Contants

        private const string ElementGrid = "MyGrid";
        private const string ElementBookPage = "MyBookPage";

        #endregion

        #region Private Property

        private Grid _grid;
        private BookPage _bookPage;

        private int _previewContactTimestamp;
        private Point _previewContactPosition;
        private Viewport3D _viewport;

        private bool _previewCanMove;
        private bool _previewCanRotate;
        private bool _previewCanScale;
        private bool _previewIsBookEnabled;
        private bool _previewCanTurnByUser;

        private Card Card
        {
            get { return this.DataContext as Card; }
        }

        private Border Page0Border
        {
            get { return _bookPage.Page0ContentPresenter.Parent as Border; }
        }

        private Border Page1Border
        {
            get { return _bookPage.Page1ContentPresenter.Parent as Border; }
        }

        private bool IsTurning { get; set; }

        #endregion

        #region Constructor

        public CardUC()
        {
            InitializeComponent();
        }

        #endregion

        #region function

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            _grid = GetTemplateChild(CardUC.ElementGrid) as Grid;
            _bookPage = GetTemplateChild(CardUC.ElementBookPage) as BookPage;
        }

        public bool BeginFlipAnimation(RotateDirection direction, Duration dur, bool useBackgroundOnly)
        {
            if (Card == null)
            {
                // if Card isn't initialized, skip to the end
                return false;
            }

            if (useBackgroundOnly)
                BeginTransition3D(Card.FrontBackground, Card.BackBackground, direction, dur);
            else
                BeginTransition3D(Page1Border, Page0Border, direction, dur);

            return true;
        }

        public void Block(ScatterViewItem S)
        {
            if (_previewContactPosition.Y >= this.ActualHeight / 2)
            {
                Card.IsTopLeftCornerEnabled = true;
                Card.IsTopRightCornerEnabled = true;
            }
            else
            {
                Card.IsBottomLeftCornerEnabled = true;
                Card.IsBottomRightCornerEnabled = true;
            }

            _previewCanMove = S.CanMove;
            _previewCanRotate = S.CanRotate;
            _previewCanScale = S.CanScale;
            _previewIsBookEnabled = Card.IsBookEnabled;
            _previewCanTurnByUser = Card.CanTurnByUser;

            S.CanMove = false;
            S.CanRotate = false;
            S.CanScale = false;
            Card.IsBookEnabled = false;
            Card.CanTurnByUser = false;
        }

        public void UnBlock(ScatterViewItem S)
        {

            Card.IsTopLeftCornerEnabled = false;
            Card.IsTopRightCornerEnabled = false;
            Card.IsBottomLeftCornerEnabled = false;
            Card.IsBottomRightCornerEnabled = false;

            S.CanMove = _previewCanMove;
            S.CanRotate = _previewCanRotate;
            S.CanScale = _previewCanScale;
            Card.IsBookEnabled = _previewIsBookEnabled;
            Card.CanTurnByUser = _previewCanTurnByUser;
        }

        #endregion

        #region Override

        protected override void OnContactDown(ContactEventArgs e)
        {
            base.OnContactDown(e);

            ScatterViewItem S = WPFHelper.FindVisualParent<ScatterViewItem>(this);

            if (Card.IsBookEnabled && S != null && Contacts.GetContactsCaptured(S).Count == 2 && Card.IsBlocked == false)
            {
                foreach (Contact tmp in Contacts.GetContactsCaptured(S))
                {
                    tmp.Capture(this);
                }

                Card.IsBlocked = true;
                this.Block(S);               

            }
            else
            {
                    if (Card.CanTurnByUser && e.Timestamp < _previewContactTimestamp + 300
                       && e.GetPosition((UIElement)this).X < _previewContactPosition.X + 15
                       && e.GetPosition((UIElement)this).X > _previewContactPosition.X - 15
                       && e.GetPosition((UIElement)this).Y < _previewContactPosition.Y + 15
                       && e.GetPosition((UIElement)this).Y > _previewContactPosition.Y - 15)
                    {
                        RotateDirection _rotate = RotateDirection.Down;

                        if (e.Contact.GetOrientation((UIElement)this) < 45.0) _rotate = RotateDirection.Right;
                        else if (e.Contact.GetOrientation((UIElement)this) < 135.0) _rotate = RotateDirection.Up;
                        else if (e.Contact.GetOrientation((UIElement)this) < 225.0) _rotate = RotateDirection.Left;
                        else if (e.Contact.GetOrientation((UIElement)this) < 315.0) _rotate = RotateDirection.Down;
                        else if (e.Contact.GetOrientation((UIElement)this) < 360.0) _rotate = RotateDirection.Right;

                        Card.Turn(_rotate);
                        _previewContactTimestamp = 0;
                    }
                    else
                    {
                        _previewContactTimestamp = e.Timestamp;
                        _previewContactPosition = e.GetPosition((UIElement)this);
                    }
            }
        }

        protected override void OnContactUp(ContactEventArgs e)
        {
            if (e.Contact.Captured != this)
                return;

            ScatterViewItem S = WPFHelper.FindVisualParent<ScatterViewItem>(this);

            if (S != null)
            {
                Card.IsBlocked = false;
                UnBlock(S);
            }
        }

        #endregion

        #region Turn

        TranslateTransform3D _translation;
        AxisAngleRotation3D _rotation0;
        AxisAngleRotation3D _rotation1;

        public void BeginTransition3D(Brush oldBrush, Brush newBrush, RotateDirection direction, Duration flipDuration)
        {
            if (IsTurning)
            {
                // stop all current animations
                _rotation0.BeginAnimation(AxisAngleRotation3D.AngleProperty, null);
                _rotation1.BeginAnimation(AxisAngleRotation3D.AngleProperty, null);
                _translation.BeginAnimation(TranslateTransform3D.OffsetXProperty, null);
                _translation.BeginAnimation(TranslateTransform3D.OffsetYProperty, null);
                EndTransition(false, _viewport.GetHashCode());
            }

            IsTurning = true;

            _viewport = new Viewport3D();
            _viewport.Measure(this.RenderSize);
            _viewport.IsHitTestVisible = false;

            _viewport.Camera = CreateCamera(10);
            _viewport.ClipToBounds = false;

            ModelVisual3D light = new ModelVisual3D();
            light.Content = GetDefaultLight();
            _viewport.Children.Add(light);

            this._grid.Children.Add(_viewport);

            Size size = this.RenderSize;

            Point3D origin = new Point3D(); // origin of 2nd face
            Vector3D u = new Vector3D(), v = new Vector3D(); // u & v vectors of 2nd face

            double angle = 0;
            Point3D rotationCenter;
            Vector3D rotationAxis;

            _translation = null;
            double angleRads = angle * Math.PI / 180;
            if (direction == RotateDirection.Left || direction == RotateDirection.Right)
            {
                rotationCenter = new Point3D(direction == RotateDirection.Left ? size.Width : 0, 0, 0);
                _translation = new TranslateTransform3D();
                DoubleAnimation x = new DoubleAnimation(direction == RotateDirection.Left ? -size.Width : size.Width, flipDuration);
                _translation.BeginAnimation(TranslateTransform3D.OffsetXProperty, x);

                rotationAxis = new Vector3D(0, 1, 0);

                if (direction == RotateDirection.Left)
                {
                    u.X = -size.Width * Math.Cos(angleRads);
                    u.Z = size.Width * Math.Sin(angleRads);

                    origin.X = size.Width;
                }
                else
                {
                    u.X = -size.Width * Math.Cos(angleRads);
                    u.Z = -size.Width * Math.Sin(angleRads);

                    origin.X = -u.X;
                    origin.Z = -u.Z;
                }
                v.Y = size.Height;
            }
            else
            {
                rotationCenter = new Point3D(0, direction == RotateDirection.Up ? size.Height : 0, 0);
                _translation = new TranslateTransform3D();
                DoubleAnimation y = new DoubleAnimation(direction == RotateDirection.Up ? -size.Height : size.Height, flipDuration);
                _translation.BeginAnimation(TranslateTransform3D.OffsetYProperty, y);

                rotationAxis = new Vector3D(1, 0, 0);

                if (direction == RotateDirection.Up)
                {
                    v.Y = -size.Height * Math.Cos(angleRads);
                    v.Z = size.Height * Math.Sin(angleRads);

                    origin.Y = size.Height;
                }
                else
                {
                    v.Y = -size.Height * Math.Cos(angleRads);
                    v.Z = -size.Height * Math.Sin(angleRads);

                    origin.Y = -v.Y;
                    origin.Z = -v.Z;
                }
                u.X = size.Width;
            }


            double endAngle = 180 - angle;
            if (direction == RotateDirection.Right || direction == RotateDirection.Up)
                endAngle = -endAngle;

            ModelVisual3D m1, m2;
            int vpHC = _viewport.GetHashCode();
            _viewport.Children.Add(m1 = MakeSide(oldBrush, new Point3D(),
                new Vector3D(size.Width, 0, 0), new Vector3D(0, size.Height, 0), endAngle, rotationCenter, rotationAxis, null, flipDuration, 0));
            _viewport.Children.Add(m2 = MakeSide(newBrush, origin, u, v, endAngle, rotationCenter, rotationAxis, delegate
            {
                EndTransition(true, vpHC);
            }, flipDuration, 1));

            var duration = new Duration(TimeSpan.FromMilliseconds(flipDuration.TimeSpan.TotalMilliseconds / 2));

            if (direction == RotateDirection.Down || direction == RotateDirection.Up)
            {
                double actualHeight = this.Card.ScatterViewItem.ActualHeight;
                Storyboard sb = new Storyboard();
                sb.Children.Add(new DoubleAnimation(0.0, duration));
                sb.Children.Add(new DoubleAnimation(actualHeight, duration) { BeginTime = duration.TimeSpan });

                foreach (var child in sb.Children)
                {
                    Storyboard.SetTargetProperty(child, new PropertyPath(ScatterViewItem.HeightProperty));
                    Storyboard.SetTarget(child, this.Card.ScatterViewItem);
                }

                sb.Begin(this.Card.ScatterViewItem);
            }

            m1.Transform = m2.Transform = _translation;
        }

        /// <summary>
        /// Makes a copy (screenshot) of both contents to flip them.
        /// Try using directly brushes to avoid memory consumption
        /// </summary>
        /// <param name="oldContent"></param>
        /// <param name="newContent"></param>
        /// <param name="direction"></param>
        /// <param name="flipDuration"></param>
        public void BeginTransition3D(FrameworkElement oldContent, FrameworkElement newContent, RotateDirection direction, Duration flipDuration)
        {
            BeginTransition3D(CreateBrush(oldContent), CreateBrush(newContent), direction, flipDuration);
        }

        private Model3DGroup GetDefaultLight()
        {
            Model3DGroup defaultLight = new Model3DGroup();

            Vector3D direction = new Vector3D(1, 1, 1);
            direction.Normalize();
            byte ambient = 108; // 108 is minimum for directional to be < 256 (for direction = [1,1,1])
            byte directional = (byte)Math.Min((255 - ambient) / Vector3D.DotProduct(direction, new Vector3D(0, 0, 1)), 255);

            defaultLight.Children.Add(new AmbientLight(Color.FromRgb(ambient, ambient, ambient)));
            defaultLight.Children.Add(new DirectionalLight(Color.FromRgb(directional, directional, directional), direction));

            return defaultLight;
        }

        private void EndTransition(bool showBookPage, int originalViewPort)
        {
            if (originalViewPort == _viewport.GetHashCode())
            {
                this._grid.Children.Remove(_viewport);
                _viewport = null;

                _translation = null;
                _rotation0 = null;
                _rotation1 = null;

                if (showBookPage)
                    Card.BookPageVisibility = Visibility.Visible;

                IsTurning = false;
            }
        }

        private ModelVisual3D MakeSide(
            Brush brush,
            Point3D origin,
            Vector3D u,
            Vector3D v,
            double endAngle,
            Point3D rotationCenter,
            Vector3D rotationAxis,
            EventHandler onCompleted,
            Duration flipDuration,
            int index)
        {
            MeshGeometry3D sideMesh = CreateMesh(origin, u, v, 1, 1, new Rect(0, 0, 1, 1));

            GeometryModel3D sideModel = new GeometryModel3D();
            sideModel.Geometry = sideMesh;

            sideModel.Material = new DiffuseMaterial(brush);

            AxisAngleRotation3D rotation = new AxisAngleRotation3D(rotationAxis, 0);

            if (index == 0)
                _rotation0 = rotation;
            else
                _rotation1 = rotation;

            sideModel.Transform = new RotateTransform3D(rotation, rotationCenter);

            DoubleAnimation da = new DoubleAnimation(endAngle, flipDuration);
            if (onCompleted != null)
                da.Completed += onCompleted;

            rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, da);

            ModelVisual3D side = new ModelVisual3D();
            side.Content = sideModel;
            return side;
        }

        // Generates a flat mesh starting at origin with sides equal to u and v vecotrs
        public static MeshGeometry3D CreateMesh(Point3D origin, Vector3D u, Vector3D v, int usteps, int vsteps, Rect textureBounds)
        {
            u = 1.0 / usteps * u;
            v = 1.0 / vsteps * v;

            MeshGeometry3D mesh = new MeshGeometry3D();

            for (int i = 0; i <= usteps; i++)
            {
                for (int j = 0; j <= vsteps; j++)
                {
                    mesh.Positions.Add(origin + i * u + j * v);

                    mesh.TextureCoordinates.Add(new Point(textureBounds.X + textureBounds.Width * i / usteps,
                                                          textureBounds.Y + textureBounds.Height * j / vsteps));

                    if (i > 0 && j > 0)
                    {
                        mesh.TriangleIndices.Add((i - 1) * (vsteps + 1) + (j - 1));
                        mesh.TriangleIndices.Add((i - 0) * (vsteps + 1) + (j - 0));
                        mesh.TriangleIndices.Add((i - 0) * (vsteps + 1) + (j - 1));

                        mesh.TriangleIndices.Add((i - 1) * (vsteps + 1) + (j - 1));
                        mesh.TriangleIndices.Add((i - 1) * (vsteps + 1) + (j - 0));
                        mesh.TriangleIndices.Add((i - 0) * (vsteps + 1) + (j - 0));
                    }
                }
            }
            return mesh;
        }

        // Returns a clone of the element and hides it in the main tree
        protected static Brush CreateBrush(FrameworkElement content)
        {
            VisualBrush brush = new VisualBrush(content);
            brush.ViewportUnits = BrushMappingMode.Absolute;
            RenderOptions.SetCachingHint(brush, CachingHint.Cache);
            RenderOptions.SetCacheInvalidationThresholdMinimum(brush, 40);

            return brush;
        }

        protected virtual Camera CreateCamera(double fov)
        {
            Size size = this.RenderSize;
            return new PerspectiveCamera(new Point3D(size.Width / 2, size.Height / 2, -size.Width / Math.Tan(fov / 2 * Math.PI / 180) / 2),
                                         new Vector3D(0, 0, 1),
                                         new Vector3D(0, -1, 0),
                                         fov);
        }

        #endregion

        #region OpacityMask

        public void SetOpacityMaskPoint(ContactEventArgs e)
        {
            var pos = e.GetPosition(this);

            var br = new RadialGradientBrush();
            br.MappingMode = BrushMappingMode.Absolute;
            if (e.Contact.GetOrientation(this) <= 180.0)
                pos.Y += 30;
            else
                pos.Y -= 30;

            br.Center = pos;
            br.GradientOrigin = pos;
            br.RadiusX = 30;
            br.RadiusY = 30;
            br.GradientStops.Add(new GradientStop(Colors.Transparent, 0));
            br.GradientStops.Add(new GradientStop(Colors.Black, 1));

            br.Freeze();

            _grid.Background = Card.BackBackground;

            Page0Border.OpacityMask = br;
        }

        public void SetOpacityMaskLine(ContactEventArgs e)
        {
            var pos = e.GetPosition(this);

            if (pos.Y < 0) return;
            //var pos = e.GetPosition(cv);
            var br = new LinearGradientBrush();
            br.MappingMode = BrushMappingMode.Absolute;
            br.StartPoint = new Point(0, 0);
            br.EndPoint = new Point(0, pos.Y);
            br.GradientStops.Add(new GradientStop(Colors.Transparent, 0));
            br.GradientStops.Add(new GradientStop(Colors.Black, 1));

            br.Freeze();

            _grid.Background = Card.BackBackground;

            Page0Border.OpacityMask = br;
        }

        public void CancelOpacityMask()
        {
            _grid.Background = Brushes.Transparent;

            Page0Border.OpacityMask = null;
        }

        #endregion

    }
}
